#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>
#include "include/libmod.h"
#include "include/modid.h"
#include "include/vmerr.h"
#include "include/nr_excp.h"
#include "mod_chrdev.h"

/*
 * ## Biscuit-Console 
 * | NAME/Bits | RESERVE[31:5] |      [4]     |   [3]     |    [2]   |     [1]     |       [0]       |
 * |-----------|---------------|--------------|-----------|----------|-------------|-----------------|
 * | Cons_CSR  |               | Driver_Ready | Dev_Ready | IO_Ready | RECV_Ready  |    SEND_Ready   |
 * | Cons_RECV |                                         DATA                                        |
 * | Cons_SEND |                                         DATA                                        |
 * 
 * - RECV_Ready: 输入就绪指示位,设备置1告知OS Cons_RECV有效,OS需读数据。OS置0告知设备可读数据
 * - SEND_Ready: 输出就绪指示位,OS置1告知设备 Cons_SEND有效,设备需写数据。设备置0告知OS可写数据
 * - 输入数据: 键盘输入 -> 设备等待RECV_Ready=0 -> 设备写Cons_RECV=ch -> 设备写RECV_Ready=1 -> 发出中断 -> 
 *   OS处理中断,读Cons_RECV -> OS写RECV_Ready=0
 * - 输出数据: OS等待SEND_Ready=0 -> OS写入Cons_SEND=ch -> OS写入SEND_Ready=1 -> 设备读SEND_Ready=1 -> 
 *   设备读Cons_SEND=ch -> 打印ch -> 设备写SEND_Ready=0
 * - PS: Console Send只有同步模式,Receive只有中断模式
 */

struct chrdev_ctx {
    struct {
        uint32 ctl;
        uint32 send;
        uint32 recv;
    } csr;
    struct {
        struct termios old_tio;
        struct termios tio;
    } term;
    struct environ *env;
    struct {
        struct pollfd p;
        // 约定buf不为0时执行输入/输出
        int8 ibuf;
        int8 obuf;
        pthread_t ith;
        pthread_mutex_t ilk;
    } io;
};

status chrdev_init (struct environ * env, struct module * mod);
status chrdev_start (struct environ * env, struct module * mod);
status chrdev_stop (struct environ * env, struct module * mod);
status chrdev_pause (struct environ * env, struct module * mod);
status chrdev_resume (struct environ * env, struct module * mod);
status chrdev_ctrl (struct environ * env, struct module * mod, uint64 arg);
status chrdev_csrrw (struct dev_csrrw_req req);
static void * in_thread (void * arg); // 输入线程
static void do_output (struct chrdev_ctx * this);

static void *
in_thread (void * arg) {
    struct chrdev_ctx * this = (struct chrdev_ctx *) arg;
    this->io.p.fd = STDIN_FILENO;
    this->io.p.events = POLLIN;
    if (this->io.ibuf != 0) goto trans;
    while (poll (&this->io.p, 1, INPOLL_TIME) == 1 && (this->io.p.events & POLLIN)) {
        this->io.ibuf = 0;
        read (STDIN_FILENO, &this->io.ibuf,1);
        trans:
        pthread_mutex_lock (&this->io.ilk);
        //printf ("Waiting for RECV_READY=0.\n");
        while (RECV_READY(this->csr.ctl)) {usleep(100);}
        //printf ("RECV_READY=0, contnue.\n");
        this->csr.recv = (uint32)this->io.ibuf;
        this->io.ibuf = 0;
        SET_RECV_READY(this->csr.ctl);
        raise_excep_async (this->env, INTRP_CHRDEV_GETC);
        pthread_mutex_unlock (&this->io.ilk);
    }
    pthread_exit (NULL);
}


status
chrdev_assemble (struct module * mod) {
    status stat = STAT_SUCCESS;
    struct chrdev_ctx * ctx = (struct chrdev_ctx *)malloc (sizeof (struct chrdev_ctx));
    bzero (ctx,sizeof (struct chrdev_ctx));
    if (ctx == NULL) {
        stat |= ERR_NOMEM;
        goto error;
    }
    mod->context = ctx;
    mod->init = chrdev_init;
    mod->start = chrdev_start;
    mod->stop = chrdev_stop;
    mod->pause = chrdev_pause;
    mod->resume = chrdev_resume;
    mod->modctl = chrdev_ctrl;
    mod->stat = MOD_STAT_UNINITIALIZED;
    mod->modid = CHRDEV_ID;
    strncpy (mod->name, "Biscuit-Console", MAX_MOD_NAMESZ);
    stat |= module_add_item (mod, MI_DEV_CSRRW, chrdev_csrrw);
    if (stat == STAT_SUCCESS)
        mod->magic = BISCUIT_MAGIC;
    return stat;
    error:
    return stat;
}

status
chrdev_init (struct environ * env, struct module * mod) {
    status stat = STAT_SUCCESS;
    struct chrdev_ctx * this = (struct chrdev_ctx *) mod->context;
    if (mod->stat != MOD_STAT_UNINITIALIZED) {
        stat |= ERR_STATUS;
        goto error;
    }
    bzero (this, sizeof (struct chrdev_ctx));
    this->env = env;
    if (pthread_mutex_init (&this->io.ilk, NULL)) {
        stat |= ERR_THREAD;
        goto error;
    }
    mod->stat = MOD_STAT_STOPPED;
    return stat;
    error:
    return stat;
}

status
chrdev_start (struct environ * env, struct module * mod) {
    (void) env;
    status stat = STAT_SUCCESS;
    struct chrdev_ctx * this = (struct chrdev_ctx *) mod->context;
    if (mod->stat != MOD_STAT_STOPPED) {
        stat |= ERR_STATUS;
        goto error;
    }
    if (tcgetattr (STDIN_FILENO, &this->term.old_tio)) {
        stat |= ERR_SYSTEM;
        goto error;
    }
    (void) in_thread;
    memcpy (&this->term.tio, &this->term.old_tio, sizeof (struct termios));
    this->term.tio.c_lflag &= ~(ICANON | ECHO);
    this->term.tio.c_cc[VMIN] = 1;
    this->term.tio.c_cc[VTIME] = 0;
    tcsetattr (STDIN_FILENO, TCSANOW, &this->term.tio);
    if (pthread_create (&this->io.ith, NULL, in_thread, this)) {
        stat |= ERR_THREAD;
        tcsetattr (STDIN_FILENO, TCSANOW, &this->term.old_tio);
        goto error;
    }
    mod->stat = MOD_STAT_RUNNING;
    return stat;
    error:
    return stat;
}

status
chrdev_stop (struct environ * env, struct module * mod) {
    (void) env;
    status stat = STAT_SUCCESS;
    struct chrdev_ctx * this = (struct chrdev_ctx *) mod->context;
    if (mod->stat != MOD_STAT_RUNNING) {
        stat |= ERR_STATUS;
        goto error;
    }
    if (pthread_cancel (this->io.ith)) {
        stat |= ERR_THREAD;
        goto error;
    }
    tcsetattr (STDIN_FILENO, TCSANOW, &this->term.old_tio);
    mod->stat = MOD_STAT_STOPPED;
    return stat;
    error:
    return stat;
}

status
chrdev_pause (struct environ * env, struct module * mod) {
    (void) env;
    status stat = STAT_SUCCESS;
    struct chrdev_ctx * this = (struct chrdev_ctx *) mod->context;
    if (mod->stat != MOD_STAT_RUNNING) {
        stat |= ERR_STATUS;
        goto error;
    }
    if(pthread_mutex_lock(&this->io.ilk)){
        stat |= ERR_THREAD;
        goto error;
    }
    mod->stat = MOD_STAT_PAUSED;
    return stat;
    error:
    return stat;
}

status
chrdev_resume (struct environ * env, struct module * mod) {
    (void) env;
    status stat = STAT_SUCCESS;
    struct chrdev_ctx * this = (struct chrdev_ctx *) mod->context;
    if (mod->stat != MOD_STAT_PAUSED) {
        stat |= ERR_STATUS;
        goto error;
    }
    if(pthread_mutex_unlock(&this->io.ilk)){
        stat |= ERR_THREAD;
        goto error;
    }
    mod->stat = MOD_STAT_RUNNING;
    return stat;
    error:
    return stat;
}

status
chrdev_ctrl (struct environ * env, struct module * mod, uint64 arg) {
    (void) env;
    (void) mod;
    (void) arg;
    return STAT_SUCCESS;
}

static void
do_output (struct chrdev_ctx * this) {
    if (SEND_READY(this->csr.ctl)) {
    this->io.obuf = (int8) this->csr.send;
    if (this->io.obuf) {
        write (STDOUT_FILENO, &this->io.obuf, 1);
        this->io.obuf = 0;
        CLR_SEND_READY (this->csr.ctl);
    }}
}

status
chrdev_csrrw (struct dev_csrrw_req req) {
    status stat = STAT_SUCCESS;
    struct chrdev_ctx * this = (struct chrdev_ctx *) req.ctx;
    switch (req.addr)
    {
    case CSR_CONCTL: {
        if (IS_RMODE(req.rw)) {
            *req.recv = this->csr.ctl;
        }
        if (IS_WMODE(req.rw)) {
            this->csr.ctl = req.send;
            do_output (this);
        }
        break;
    }
    case CSR_CONRECV : {
        if (IS_RMODE(req.rw)) {
            *req.recv = this->csr.recv;
        }
        if (IS_WMODE(req.rw)) {
            DBG_LOG (chrdev_csrrw,"CSR.RECV.W: 0x%X", req.send);
            this->csr.recv = req.send;
        }
        break;
    } 
    case CSR_CONSEND : {
        if (IS_RMODE(req.rw)) {
            *req.recv = this->csr.send;
        }
        if (IS_WMODE(req.rw)) {
            DBG_LOG (chrdev_csrrw,"CSR.SEND.W: 0x%X", req.send);
            this->csr.send = req.send;
        }
        break;
    }
    default:
        goto nop;
        break;
    }
    req.rstat->req_ack = true;
    nop:
    return stat;
}
